#pragma once
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include "util.hpp"

typedef enum {
    UNLOGIN,
    LOGIN
} ss_status;

class session
{
public:
    session(uint64_t ssid): _ssid(ssid) {
        DLOG("session %p 被创建", this);
    }
    ~session() {
        DLOG("session %p 被销毁", this);
    }

    uint64_t ssid() { return _ssid; }
    void set_statue(ss_status status) { _status = status; }
    void set_user(uint64_t uid) { _uid = uid; }
    uint64_t get_user() { return _uid; }
    bool is_login() { return _status == LOGIN; }
    void set_timer(const wsserver_t::timer_ptr& tp) { _tp = tp; }
    wsserver_t::timer_ptr get_timer() { return _tp; }
    
private:
    uint64_t _ssid; // session id
    uint64_t _uid;  // session对于的用户id
    ss_status _status; // 用户状态：登入/未导入
    wsserver_t::timer_ptr _tp; // session关联的定时器
};

#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
using session_ptr = std::shared_ptr<session>;
class session_manager
{
public:
    session_manager(wsserver_t* server):
        _next_ssid(1), _server(server) {
        DLOG("session管理器初始化完毕");
    }
    ~session_manager() {
        DLOG("session管理器销毁完毕");
    }

    // 创建session
    session_ptr create_session(uint64_t uid, ss_status status)
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        session_ptr ssp(new session(_next_ssid));
        ssp->set_statue(status);
        ssp->set_user(uid);
        _session.insert(std::make_pair(_next_ssid, ssp));
        _next_ssid++;
        return ssp;
    }
    // 通过ssid找到对应的session
    session_ptr get_session_by_ssid(uint64_t ssid)
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        auto it = _session.find(ssid);
        if (it == _session.end()) {
            return session_ptr();
        }
        return it->second;
    }
    // 移除session
    void remove_session(uint64_t ssid)
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _session.erase(ssid);
    }
    // 添加一个已经存在的session，为下面取消定时器
    void append_session(const session_ptr& ssp)
    {
        std::unique_lock<std::mutex> _lock(_mutex);
        _session.insert(std::make_pair(ssp->ssid(), ssp));
    }
    // 设置session的过期时间，单位是毫秒
    void set_session_expire_time(uint64_t ssid, int ms)
    {
        // 依赖于websocketpp的定时器来完成session生命周期的管理
        // 在http短连接通信（登入、注册）session应该具备生命周期，指定时间无通信后删除
        // 在websocket长连接通信（游戏大厅，游戏房间），session应该是永久存在的

        // session不存在时直接退出
        session_ptr ssp = get_session_by_ssid(ssid);
        if (ssp.get() == nullptr) {
            return;
        }
        // 获取session的定时器
        wsserver_t::timer_ptr tp = ssp->get_timer();
        if (tp.get() == nullptr && ms == SESSION_FOREVER) {
            // 1. 没有定时器说明session永久存在，现在设置为永久存在
            return; // 本来就是，则直接退出即可
        } else if (tp.get() == nullptr && ms != SESSION_FOREVER) {
            // 2. 没有定时器说明session永久存在，现在设置为指定时间后删除session
            // 设置定时任务，返回定时器，可以通过定时器来取消定时任务
            wsserver_t::timer_ptr tmp = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssid));
            // 给session设置定时器
            ssp->set_timer(tmp); 
        } else if (tp.get() != nullptr && ms == SESSION_FOREVER) {
            // 3. 有定时器说明session将要被删除，现在将session设置为永久存在，只需要通过定时器取消定时任务即可
            // 注意：取消定时任务会导致底层直接执行定时任务，进而导致session被删除
            tp->cancel();
            // 为session设置空的定时器，表明session永久存在，后续要进行判断
            ssp->set_timer(wsserver_t::timer_ptr()); 
            // 需要再删除session后，重新添加session到哈希表中，但是tp->cancel()并不是立即被执行的
            // 可能是在添加session到哈希表之后才被执行，就可能导致session在哈希表中不存在
            // 解决方法：添加定时任务，让定时任务帮我们执行添加session到哈希表中
            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));
        } else if (tp.get() != nullptr && ms != SESSION_FOREVER) {
            // 4. 有定时器说明session将要被删除，现在要重置session的删除时间
            // 取消定时器
            tp->cancel(); 
            ssp->set_timer(wsserver_t::timer_ptr());
            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));
            // 重新给session添加定时销毁任务
            wsserver_t::timer_ptr tmp = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssp->ssid()));
            // 重新设置session关联的定时器
            ssp->set_timer(tmp);
        }
    }

private:
    uint64_t _next_ssid; // 下个会话id
    std::mutex _mutex; // 多线程中保护_next_ssid和哈希表
    std::unordered_map<uint64_t, session_ptr> _session; // 通过会话id找到对应的会话信息
    wsserver_t* _server; // 通过里面的set_timer设置定时器
};